# ToCriteriaPartTreeTransformer.java

    package org.codefilarete.stalactite.spring.repository.query.derivation;

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;

    import org.codefilarete.reflection.AccessorChain;
    import org.codefilarete.stalactite.engine.EntityCriteria.LimitAware;
    import org.codefilarete.stalactite.engine.EntityCriteria.OrderByChain;
    import org.codefilarete.stalactite.engine.EntityCriteria.OrderByChain.Order;
    import org.codefilarete.stalactite.engine.runtime.projection.ProjectionQueryCriteriaSupport;
    import org.codefilarete.stalactite.engine.runtime.query.EntityCriteriaSupport;
    import org.codefilarete.stalactite.engine.runtime.query.EntityQueryCriteriaSupport;
    import org.codefilarete.stalactite.query.model.LogicalOperator;
    import org.springframework.data.domain.Sort.Direction;
    import org.springframework.data.repository.query.parser.Part;
    import org.springframework.data.repository.query.parser.PartTree;
    import org.springframework.data.repository.query.parser.PartTree.OrPart;

    /**
     * Applies a {@link PartTree} to a {@link EntityCriteriaSupport} which is expected to come from either a
     * {@link ProjectionQueryCriteriaSupport} or a
     * {@link EntityQueryCriteriaSupport}
     *
     * @param <C>
     * @author Guillaume Mary
     */
    public class ToCriteriaPartTreeTransformer<C> extends AbstractDerivedQuery<C> {

    	private final PartTree partTree;

    	public ToCriteriaPartTreeTransformer(PartTree partTree, Class<C> entityType) {
    		super(entityType);
    		this.partTree = partTree;
    	}

    	public Condition applyTo(EntityCriteriaSupport<C> entityCriteriaSupport, OrderByChain<C, ?> orderByChain, LimitAware<?> limitAware) {
    		Processor processor = new Processor(entityCriteriaSupport, orderByChain);
    		partTree.forEach(processor::append);
    		if (partTree.getSort().isSorted()) {
    			processor.appendSort(partTree);
    		}
    		if (partTree.getMaxResults() != null) {
    			limitAware.limit(partTree.getMaxResults());
    		}
    		return new Condition(processor.conditions);
    	}

    	public static class Condition {

    		private final List<Criterion> condition;

    		public Condition(List<Criterion> condition) {
    			this.condition = condition;
    		}

    		public void consume(Object[] arguments) {
    			int argumentIndex = 0;
    			for (Criterion criterion : condition) {
    				criterion.setValue(arguments, argumentIndex);
    				argumentIndex += criterion.argumentCount;
    			}
    		}
    	}

    	private class Processor {

    		private EntityCriteriaSupport<C> currentSupport;
    		private final OrderByChain<C, ?> orderByChain;
    		private final List<Criterion> conditions = new ArrayList<>();

    		public Processor(EntityCriteriaSupport<C> currentSupport, OrderByChain<C, ?> orderByChain) {
    			this.orderByChain = orderByChain;
    			this.currentSupport = currentSupport;
    		}

    		private void appendSort(PartTree tree) {
    			tree.getSort().iterator().forEachRemaining(order -> {
    				AccessorChain<C, Object> orderProperty = convertToAccessorChain(order);
    				orderByChain.orderBy(orderProperty, order.getDirection() == Direction.ASC ? Order.ASC : Order.DESC, order.isIgnoreCase());
    			});
    		}

    		private void append(OrPart part) {
    			boolean nested = false;
    			if (part.stream().count() > 1) {    // "if" made to avoid extra parenthesis (can be considered superfluous)
    				nested = true;
    				this.currentSupport = currentSupport.beginNested();
    			}
    			Iterator<Part> iterator = part.iterator();
    			if (iterator.hasNext()) {
    				append(iterator.next(), LogicalOperator.OR);
    			}
    			iterator.forEachRemaining(p -> this.append(p, LogicalOperator.AND));
    			if (nested) {    // "if" made to avoid extra parenthesis (can be considered superfluous)
    				this.currentSupport = currentSupport.endNested();
    			}
    		}

    		private void append(Part part, LogicalOperator orOrAnd) {
    			AccessorChain<C, Object> getter = convertToAccessorChain(part.getProperty());
    			Criterion criterion = convertToCriterion(part);
    			this.currentSupport.add(orOrAnd, getter.getAccessors(), criterion.condition);
    			this.conditions.add(criterion);
    		}
    	}
    }